package xyz.scootaloo.mono.base.lang

import xyz.scootaloo.mono.base.lang.CallingResult.StaticFactoryMethods

/**
 * 对service调用结果的封装
 *
 * 通常service方法返回布尔值表示是否调用成功, 但这种方式会忽略掉方法中捕获的异常信息, 例如下面这种情况
 * ```kotlin
 * fun checkAccount(username: String, password: String): Boolean {
 *     return try {
 *         check(username, password)
 *         true
 *     } catch(ex: PasswordMistakeException) {
 *         false
 *     } catch(otherException: Exception) {
 *         false
 *     }
 * }
 * ```
 * 这样假如调用者想对这其中的异常进行进一步处理就无能为力了. 通过 [CallingResult] 进行封装, 上面的代码可以改成这样
 * ```kotlin
 * fun checkAccount(username: String, password: String): CallingResult<*> {
 *     return try {
 *         check(username, password)
 *         CallingResult.success()
 *     } catch (ex: Exception) {
 *         CallingResult.fail(ex)
 *     }
 * }
 * ```
 * 调用者
 * ```kotlin
 * val res = checkAccount(username, password)
 * if (!res.success && res.hasException()) {
 *     handle(res.exception)
 * }
 * ```
 *
 * [StaticFactoryMethods] 便捷的工厂方法, 一般通过这些工厂方法创建 [CallingResult] 对象
 *
 * @see Call 类型别名
 * @author flutterdash@qq.com
 * @since 2021/7/27 13:35
 *
 * @param T 假如方法调用包含值, T是值的类型
 * @param success 方法是否调用成功
 * @param exception 方法调用中遇到的异常
 */
data class CallingResult<T>(
    val success: Boolean,
    val result: T?,
    val exception: Exception?
) {

    /**
     * 强制获取值
     *
     * @exception NullPointerException 假如[success]为false, 则抛出空指针异常
     */
    fun get(): T {
        return result!!
    }

    fun hasException(): Boolean = exception != null

    companion object StaticFactoryMethods {
        fun <T> success(): CallingResult<T> =
            CallingResult(true, null, null)

        fun <T> success(res: T): CallingResult<T> =
            CallingResult(true, res, null)

        fun <T> fail(): CallingResult<T> =
            CallingResult(false, null, null)

        fun <T> fail(instead: T): CallingResult<T> =
            CallingResult(false, instead, null)

        fun <T> fail(exception: Exception): CallingResult<T> =
            CallingResult(false, null, exception)
    }
}
